package cheme.ProcessModel;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import cheme.ProcessModel.Errors.ChemeInvalidAdditionError;
import cheme.ProcessModel.Errors.ChemeInvalidIndexError;
import cheme.ProcessModel.Errors.ChemeInvalidRemovalError;
import cheme.ProcessModel.Errors.ChemeNullArgError;
import cheme.ProcessModel.ProcessUnits.ProcessUnit;
import cheme.ProcessModel.ProcessUnits.Streams.Tags.IGeneralTagComponent;
import cheme.ProcessModel.ProcessUnits.Streams.Tags.ITagComponent;
import cheme.ProcessModel.ProcessUnits.Streams.Tags.OverallTagComponent;
import cheme.ProcessModel.ProcessUnits.Streams.Tags.Tag;
import cheme.ProcessModel.ProcessUnits.Streams.Tags.TagComponentList;

public abstract class AbstractParent<T extends IChild> extends AbstractChild implements IParent<T>
{
	/*
	 * Serialization version identifier. (Set to default for now)
	 */
	
	private static final long serialVersionUID = 1L;
	
	public static final List<IChild> EMPTY_CHILDREN = new ArrayList<IChild>(0);

	public AbstractParent(IModelElement parent)
	{
		super(parent);
	}

	public List getModelChildAndAnnotations()
	{
		List list = new ArrayList();
		list.addAll(getChildren());
		list.addAll(getAnnotations());
		return list;
	}
	
	private List<T> children = null;
	
	public void add(T child)
	{
		addInternal(child);
		firePropertyChange(IPropertyAware.P_CHILD, null, child);
	}

	public void add(T child, int index)
	{
		addInternal(child, index);
		firePropertyChange(IPropertyAware.P_CHILD, null, child);
	}
	
	public void addAll(List<T> list)
	{
		if(null != list && list.size() != 0)
		{
			addAllInternal(list);
			firePropertyChange(IPropertyAware.P_CHILDREN, null, list.get(list.size() - 1));
		}
	}
	
	public void addAll(List<T> list, int index)
	{
		if(null != list && list.size() != 0)
		{
			addAllInternal(list, index);
			firePropertyChange(IPropertyAware.P_CHILDREN, null, list.get(list.size() - 1));
		}
	}
	
	public int size()
	{
		return getChildren().size();
	}
	
	public Iterator<T> iterator()
	{
		return getChildren().iterator();
	}

	public List<T> getChildren()
	{
		if(null == children)
		{
			children = new ArrayList<T>();
		}
		
		return children;
	}
	
	public T get(int index)
	{
		if(index < 0 || getChildren().size() < index)
		{
			throw new ChemeInvalidIndexError();
		}
		
		try
		{
			return (T)getChildren().get(index);
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		
		return (T)getChildren().get(index);
	}

	public void reorderChild(T child, int index)
	{
		moveComponentInternal(child, index);
		firePropertyChange(IPropertyAware.P_REORDER, null, child);
	}

	public void replace(T remove, List<T> add)
	{
		int insertionIndex = indexOf(remove);
		removeInternal(remove);
		addAllInternal(add, insertionIndex);
		
		firePropertyChange(IPropertyAware.P_CHILDREN, remove, add.get(0));
	}
	
	public void removeAll(List<T> remove)
	{
		for(T r : remove)
		{
			removeInternal(r);
		}
		
		firePropertyChange(IPropertyAware.P_CHILDREN, remove.get(0), null);
	}
	
	public void remove(T child)
	{
		/************* Code to handle removal of the element ans console view messages******************/
		if (child instanceof Tag) 
		{
			
			Tag tu = (Tag)child;
			Iterator<IChild> StreamChilditr = tu.getChildren().iterator();
			Iterator<IChild> Tempiterator = tu.getChildren().iterator(); // For getting the type
			while(StreamChilditr.hasNext())
			{
				if(Tempiterator.next() instanceof OverallTagComponent)
				{
					ConsoleModel.checkModel(StreamChilditr.next());
				}
				else 
				{
					// tag component list . iterate this list for tag components
					TagComponentList subComponent = (TagComponentList)StreamChilditr.next();
					Iterator<IGeneralTagComponent> Listiterator = subComponent.getChildren().iterator();
					ITagComponent childComponent = null;
					while(Listiterator.hasNext())
					{				
						childComponent = (ITagComponent)Listiterator.next();
						if(childComponent.isInitial()) break; 
						ConsoleModel.checkModel(childComponent);
					}
				}
			}
		}
		
		/************* Code to handle removal of the element ans console view messages ends ******************/
		
		removeInternal(child);
		firePropertyChange(IPropertyAware.P_CHILD, child, null);
	}
	
	public boolean contains(T child)
	{
		return getChildren().contains(child);
	}
	
	public int indexOf(T child)
	{
		return getChildren().indexOf(child);
	}

	public void set(int index, T value)
	{
		set(index, value, null);
	}
	
	public void set(int index, T value, String updateProperty)
	{
		if(getChildren().size() <= index)
		{
			add(value, index);
		}
		else if(!get(index).equals(value))
		{
			T old = get(index);
			
			setInternal(index, value);
			
			firePropertyChange(updateProperty, old, value);
		}
	}
	
	/*--------------------------------------------------------------------------
	 * 
	 */
//	public void reflectiveAdd(T child)
//	{		
//		try
//		{
//			Method method = getClass().getMethod("reflectiveAdd", new Class[] { child.getClass() });
//
//			if (null != method)
//			{
//				method.invoke(this, new Object[] {child});
//			}
//			else
//			{
//				defaultReflectiveAdd(child);
//			}
//		}
//		catch (NoSuchMethodException e)
//		{
//			this.defaultReflectiveAdd(child);
//		}
//		catch (InvocationTargetException e)
//		{
//			this.defaultReflectiveAdd(child);
//		}   
//		catch (IllegalAccessException e)
//		{
//			this.defaultReflectiveAdd(child);
//		} 
//	}
//	
//	protected void defaultReflectiveAdd(T child)
//	{
//		add(child); 
//	}
	
	/*--------------------------------------------------------------------------
	 * Internal non-event-fireing methods
	 */
	
	protected void addInternal(T child)
	{
		if(null != child)
		{
			if(getChildren().contains(child))
			{
				throw new ChemeInvalidAdditionError();
			}
			
			child.setParent(this);
			hookChild(child);

			getChildren().add(child);
		}
	}

	protected void addInternal(T child, int index)
	{
		if(null != child)
		{
			if(getChildren().contains(child))
			{
				throw new ChemeInvalidAdditionError();
			}
			
			child.setParent(this);
			hookChild(child);
			
			if(-1 != index)
			{
				getChildren().add(index, child);
			}
			else
			{
				getChildren().add(child);
			}
		}
	}
	
	public void addAllInternal(List<T> list)
	{
		for(T child : list)
		{
			if(null != child)
			{
				if(getChildren().contains(child))
				{
					throw new ChemeInvalidAdditionError();
				}
				
				child.setParent(this);
				hookChild(child);
			}
		}
		
		getChildren().addAll(list);
	}
	
	public void addAllInternal(List<T> list, int index)
	{
		for(T child : list)
		{
			if(null != child && getChildren().contains(child))
			{
				throw new ChemeInvalidAdditionError();
			}
			
			child.setParent(this);
			hookChild(child);
		}
		
		if(-1 != index)
		{
			getChildren().addAll(index, list);
		}
		else
		{
			getChildren().addAll(list);
		}
	}

	protected void moveComponentInternal(T child, int index)
	{
		getChildren().remove(child);
		addInternal(child, index);
	}

	protected void removeInternal(T child)
	{
		if((null != child && getChildren().contains(child)))
		{
			//child.setParent(null); //TODO: is it safe to leave the old parent in place?
			unhookChild(child);
			
			getChildren().remove(child);
		}
		else
		{
			throw new ChemeInvalidRemovalError();
		}
	}
	
	protected void hookChild(T child)
	{
		ModelEnv.getInstanceFor(this).hookModelElement(child);
		child.addPropertyChangeListener(this);
	}
	
	protected void unhookChild(T child)
	{
		ModelEnv.getInstanceFor(this).unhookModelElement(child);
		child.removePropertyChangeListener(this);
	}
	
	@Override
	public String toString()
	{
		String string = "";
		
		Iterator<T> itr = getChildren().iterator();
		T child = null;
		
		if(itr.hasNext())
		{
			child = itr.next();
			string += child.toString();
		}
		
		while(itr.hasNext())
		{
			string += getToStringSeparator();
			child = itr.next();
			string += child.toString();
		}
		
		return string;
	}
	
	protected String getToStringSeparator()
	{
		return " ";
	}
	
	protected void setInternal(int index, T value)
	{
		if(getChildren().size() < index)
		{
			throw new ChemeNullArgError();
		}
		
		remove(get(index));
		
		if(null != value)
		{
			add(value, index);
		}
	}
}
